#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::_CCInst;

//获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t ByteSize)
{
	//查看当前的SpanList是否还有未分配对象的span
	Span* start = list.Begin();
	while (start != list.End())
	{
		if (start->freeLists != nullptr)
			return start;
		else
			start = start->next;
	}

	// 先对每个桶进行解锁，其他线程释放空间回来，不会阻塞
	list.BucketMutex.unlock();

	//走到这里说明没有空闲span了，需要向PageCache申请
	//这里需要加锁，防止某个地方多次申请了空间
	PageCache::GetInstance().getMutex().lock();
	Span* span = PageCache::GetInstance().newSpan(SizeClass::NumMovePage(ByteSize));
	span->_isUse = true;
	PageCache::GetInstance().getMutex().unlock();

	// 获取了新的span，计算它的起始地址，以及大块内存的大小(字节数)
	// 获取实际分配的字节数，根据实际需要的ByteSize切割自由链表
	// PageId是大块内存的起始页号，start1是获取起始页的偏移，以免将刚分配的大块内存放在了之前被使用了但是还未归还的位置上
	// CentralCache有多个链表，分配了多个不同大小的桶，它的结构是一个链式结构，每个桶下面挂了多个span，
	// 每个span下面又挂了一些内存页，这些页使用自由链表来管理
	// n是当前span的下面的页的数量，左移可以得到在span下面的偏移量，然后这两个相加就是这段大块内存在一个桶内的实际偏移
	//std::cout << "CentralCache.cpp 34 " << "解锁" << std::endl;


	char* block_start = (char*)(span->PageId << PAGE_SHIFT);
	char* block_end = block_start + (span->n << PAGE_SHIFT);

	// 把大块内存切割成自由链表连接起来
	span->freeLists = block_start;
	char* cur = block_start;
	char* next = cur + ByteSize;
	while (next < block_end)
	{
		ChangeOneToTwoPointer(cur) = next;
		cur = next;
		next += ByteSize;
	}
	
	ChangeOneToTwoPointer(cur) = nullptr;

	//上面这部分并没有设计实际对桶的操作，所以不需要加锁
	//在将分配的span挂回桶内时在加锁
	list.BucketMutex.lock();
	list.PushFront(span);

	return span;
}
//从中心缓存获取一定数量的对象给thread_cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t BatchNum, size_t size)
{
	size_t Index = SizeClass::Index(size);

	spanlist[Index].BucketMutex.lock();
	//获取大块的内存
	//std::cout << "CentralCache.cpp 68 " << "获取大块内存" << std::endl;

	Span* span = _CCInst.GetOneSpan(spanlist[Index], size);
	
	//判断获取的是否为空以及Span下面是否有空间可以分配
	assert(span);
	assert(span->freeLists);
	
	start = span->freeLists, end = start;
	size_t i = 0;
	//能走到这里说明是有空间的，最少也得获取1个对象
	size_t ActualGetObjNum = 1;
	while (ChangeOneToTwoPointer(end) && i < BatchNum - 1)
	{
		end = ChangeOneToTwoPointer(end);
		i += 1;
		ActualGetObjNum += 1;
	}
	span->freeLists = ChangeOneToTwoPointer(end);
	ChangeOneToTwoPointer(end) = nullptr;
	
	span->UseCount += ActualGetObjNum;
	spanlist[Index].BucketMutex.unlock();

	return ActualGetObjNum;
}

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	// 把归还回来的空间重新放到中心缓存对应的span位置,没人使用这个span就还给页缓存
	// 先对齐，然后对这个桶加锁
	size_t index = SizeClass::Index(size);
	spanlist[index].BucketMutex.lock();
	
	while (start)
	{
		void* next = ChangeOneToTwoPointer(start);
		// 找到该对象在页缓存的什么位置，在
		// 建立id和span的映射，方便central cache回收小块内存时，查找对应的span，配合下面这个for循环程序会好理解一点
		//for (PAGEID i = 0; i < kspan->n; i++)
		//{
		//	// 将一个桶内的span管理的每一页都在哈希表内做一个映射，方便centralcache回收时知道哪些页的使用状态
		//	_idSpanMap[kspan->PageId + i] = kspan;
		//}
		Span* span = PageCache::GetInstance().MapObjectToSpan(start);
		// 为什么要从页缓存中找，因为程序刚开始时，线程缓存，中心缓存，页缓存都没有空间，都需要向堆申请
		// 中心缓存调用newSpan返回了一个k页的span，所以找该对象在中心缓存的什么位置就需要通过转换页id
		// 从start开始遍历，找到要归还的空间的结束位置
		// 将双向链表连接起来
		ChangeOneToTwoPointer(start) = span->freeLists;
		span->freeLists = start;
		// 把对象放回到span的freeList内同时让use_count减一，当减到0时说明这个span已经不再被使用了，将这个span还给页缓存		span->UseCount--;

		if (span->UseCount == 0)
		{
			// 将空间还给页缓存
			spanlist[index].Erase(span);
			span->freeLists = nullptr;
			span->prev = span->next = nullptr;
			//解锁，把span还给pageCache
			spanlist[index].BucketMutex.unlock();

			PageCache::GetInstance().getMutex().lock();
			PageCache::GetInstance().ReleaseSpanToPageCache(span);
			PageCache::GetInstance().getMutex().unlock();

			spanlist[index].BucketMutex.lock();
		}
		start = next;
	}
	// 释放桶锁
	spanlist[index].BucketMutex.unlock();
}